import json
import sys
import re
import time

from mininet.net import Mininet
from mininet.node import RemoteController, OVSSwitch, UserSwitch
from mininet.cli import CLI
from mininet.link import TCLink, TCULink
from mininet.log import setLogLevel

from base_net import BaseNet
sys.path.append("../traffic")
sys.path.append("../config")
import read_traffic as rt
import run_config as rc


def unicode_convert(ori_dict):
    if isinstance(ori_dict, dict):
        return {unicode_convert(key): unicode_convert(value) for key, value in ori_dict.iteritems()}
    elif isinstance(ori_dict, list):
        return [unicode_convert(element) for element in ori_dict]
    elif isinstance(ori_dict, unicode):
        return ori_dict.encode('utf-8')
    else:
        return ori_dict


def topology(remoteip, ofversion, pod_num, which_row, traffic_num):
    topo_file = '../config/' + str(pod_num) + 'pod_topo.json'
    traffic_file = '../traffic/' + str(pod_num) + 'pod_traffic.csv'
    topo_bw = rt.get_config('../traffic/' + str(pod_num) + 'pod_topo.csv', which_row, pod_num)

    # TODO: dynamicly create pod_threshold_rules.sh
    switch_rules_file = '../switch_rules/' + str(pod_num) + 'pod_threshold_rules.sh'
    # switch_rules_file = '../switch_rules/' + str(pod_num) + 'pod_direct_rules.sh'
    # switch_rules_file = '../switch_rules/' + str(pod_num) + 'pod_weight_rules.sh'
    # switch_rules_file = '../switch_rules/' + str(pod_num) + 'pod_threshold_vr_rules.sh'

    print('Runtime configuration:')
    print('The number of pod:', pod_num)
    print(topo_file)
    print('What is the line of the topo and threshold file: ', which_row)
    print(traffic_file)
    print(switch_rules_file)
    print('------')

    net = BaseNet(
        controller=RemoteController,
        switch=UserSwitch,
        autoStaticArp=True,
        link=TCULink,
    )
    c1 = net.addController("c1",controller=RemoteController,ip=remoteip,port=6653)
    switch_dict = {}
    switch_list = []

    with open(topo_file, 'r') as load_f:
        load_dict = json.load(load_f)

    # if use python3 to run, do not need unicode_convert
    # load_dict = unicode_convert(load_dict)

    for pod in load_dict['pod_list']:
        sw = net.addSwitch(pod['name'], protocols=ofversion)
        switch_list.append(sw)
        switch_dict[pod['name']] = sw
        # obtain switch name number, eg: s1->1
        switch_num = int(re.findall(r'\d+', pod['name'])[0])
        
        for host in pod['host_list']:
            h = net.addHost(host['name'], ip=host['ip'], mac=host['mac'])
            net.addLink(sw, h, switch_num, bw=3)

     
    for link in load_dict['link_graph']:
        switch_begin_num = int(re.findall(r'\d+', link['begin'])[0])
        switch_end_num = int(re.findall(r'\d+', link['end'])[0])
        # Uses bw to change DCN topology with ofsoftswitch13. TCULink is required to set up link capacity.
        # Since mininet is slow, the total egress/ingress capacity for each pod is set as 7Mbps.
        net.addLink(
            node1 = switch_dict[link['begin']],
            node2 = switch_dict[link['end']],
            port1 = switch_end_num,
            port2 = switch_begin_num,
            bw = float(topo_bw[switch_begin_num - 1][switch_end_num - 1])
        )

    print("***Building network.")
    net.build()
    for sw in switch_list:
        sw.start([c1])
    
    print("***Starting network")
    c1.start()

    print("wait...")
    time.sleep(2)
    # Any host can add all flow tables
    print("***Adding switch rules")
    switch_list[0].cmd('sh ' + switch_rules_file)
    print("***Finished adding switch rules")

    traffic_arr = rt.get_traffic(traffic_file)
    traffic_arr = traffic_arr[which_row * traffic_num : (which_row + 1) * traffic_num]
    net.simple_CLI(traffic_arr, which_row)

    print("***Stoping network")
    net.stop()


if __name__ == "__main__":
    setLogLevel("info")

    pod_num = rc.pod_num
    which_row = rc.which_row
    traffic_num = rc.a_state_corresponding_traffic_num

    topology(
        "127.0.0.1",
        "OpenFlow13",
        pod_num,
        which_row,
        traffic_num,
    )
